﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;


namespace XMouse360
{
	public class Keymap
	{
		public string Name;
		public List<Event> A;
		public List<Event> B;
		public List<Event> X;
		public List<Event> Y;
		public List<Event> Back;
		public List<Event> Start;
		public List<Event> LeftShoulder;
		public List<Event> RightShoulder;
		public List<Event> LeftStickPress;
		public List<Event> RightStickPress;
		public List<Event> DPadUp;
		public List<Event> DPadDown;
		public List<Event> DPadLeft;
		public List<Event> DPadRight;
		public List<Event> LeftStick;
		public List<Event> RightStick;
		public List<Event> LeftTrigger;
		public List<Event> RightTrigger;
		public List<Event> Connected;
		public bool IsDefault;
		public uint rank;

		public Keymap()
		{
			Name = "";
			A = new List<Event>();
			B = new List<Event>();
			X = new List<Event>();
			Y = new List<Event>();
			Back = new List<Event>();
			Start = new List<Event>();
			LeftShoulder = new List<Event>();
			RightShoulder = new List<Event>();
			LeftStickPress = new List<Event>();
			RightStickPress = new List<Event>();
			DPadUp = new List<Event>();
			DPadDown = new List<Event>();
			DPadLeft = new List<Event>();
			DPadRight = new List<Event>();
			LeftStick = new List<Event>();
			RightStick = new List<Event>();
			LeftTrigger = new List<Event>();
			RightTrigger = new List<Event>();
			Connected = new List<Event>();
			IsDefault = false;
			rank = uint.MaxValue;
		}

		public override bool Equals(object obj)
		{
			return base.Equals(obj);
		}
		public bool IsEqual(Keymap map)
		{
			if (!(
				map.Name.Equals(this.Name) &&
				map.rank.Equals(this.rank) &&
				map.IsDefault.Equals(this.IsDefault)
				)
				)
				return false;
			#region EventList Count Checks
			if (A.Count != map.A.Count ||
				B.Count != map.B.Count ||
				X.Count != map.X.Count ||
				Y.Count != map.Y.Count ||
				Back.Count != map.Back.Count ||
				Start.Count != map.Start.Count ||
				LeftShoulder.Count != map.LeftShoulder.Count ||
				RightShoulder.Count != map.RightShoulder.Count ||
				LeftStickPress.Count != map.LeftStickPress.Count ||
				RightStickPress.Count != map.RightStickPress.Count ||
				DPadUp.Count != map.DPadUp.Count ||
				DPadDown.Count != map.DPadDown.Count ||
				DPadLeft.Count != map.DPadLeft.Count ||
				DPadRight.Count != map.DPadRight.Count ||
				LeftStick.Count != map.LeftStick.Count ||
				RightStick.Count != map.RightStick.Count ||
				LeftTrigger.Count != map.LeftTrigger.Count ||
				RightTrigger.Count != map.RightTrigger.Count ||
				Connected.Count != map.Connected.Count
				)
				return false;
			#endregion
			#region EventList Checks

			for (int count = 0; count < A.Count; count++)
			{
				if (!(A[count].IsEqual(map.A[count])))
					return false;
			}
			for (int count = 0; count < B.Count; count++)
			{
				if (!(B[count].IsEqual(map.B[count])))
					return false;
			}
			for (int count = 0; count < X.Count; count++)
			{
				if (!(X[count].IsEqual(map.X[count])))
					return false;
			}
			for (int count = 0; count < Y.Count; count++)
			{
				if (!(Y[count].IsEqual(map.Y[count])))
					return false;
			}
			for (int count = 0; count < Back.Count; count++)
			{
				if (!(Back[count].IsEqual(map.Back[count])))
					return false;
			}
			for (int count = 0; count < Start.Count; count++)
			{
				if (!(Start[count].IsEqual(map.Start[count])))
					return false;
			}
			for (int count = 0; count < LeftShoulder.Count; count++)
			{
				if (!(LeftShoulder[count].IsEqual(map.LeftShoulder[count])))
					return false;
			}
			for (int count = 0; count < RightShoulder.Count; count++)
			{
				if (!(RightShoulder[count].IsEqual(map.RightShoulder[count])))
					return false;
			}
			for (int count = 0; count < LeftStickPress.Count; count++)
			{
				if (!(LeftStickPress[count].IsEqual(map.LeftStickPress[count])))
					return false;
			}
			for (int count = 0; count < RightStickPress.Count; count++)
			{
				if (!(RightStickPress[count].IsEqual(map.RightStickPress[count])))
					return false;
			}
			for (int count = 0; count < DPadUp.Count; count++)
			{
				if (!(DPadUp[count].IsEqual(map.DPadUp[count])))
					return false;
			}
			for (int count = 0; count < DPadDown.Count; count++)
			{
				if (!(DPadDown[count].IsEqual(map.DPadDown[count])))
					return false;
			}
			for (int count = 0; count < DPadLeft.Count; count++)
			{
				if (!(DPadLeft[count].IsEqual(map.DPadLeft[count])))
					return false;
			}
			for (int count = 0; count < DPadRight.Count; count++)
			{
				if (!(DPadRight[count].IsEqual(map.DPadRight[count])))
					return false;
			}
			for (int count = 0; count < LeftStick.Count; count++)
			{
				if (!(LeftStick[count].IsEqual(map.LeftStick[count])))
					return false;
			}
			for (int count = 0; count < RightStick.Count; count++)
			{
				if (!(RightStick[count].IsEqual(map.RightStick[count])))
					return false;
			}
			for (int count = 0; count < LeftTrigger.Count; count++)
			{
				if (!(LeftTrigger[count].IsEqual(map.LeftTrigger[count])))
					return false;
			}
			for (int count = 0; count < RightTrigger.Count; count++)
			{
				if (!(RightTrigger[count].IsEqual(map.RightTrigger[count])))
					return false;
			}
			for (int count = 0; count < Connected.Count; count++)
			{
				if (!(Connected[count].IsEqual(map.Connected[count])))
					return false;
			}
			#endregion
			return true;
		}



		public class Event
		{
			public EventTrigger EventTime;
			public string Type;
			public string Code;
			public string Data;
			public bool IsBoolean;
			public Axis SelectedAxis;
			public bool TimeAcceleration;
			public float Acceleration;
			public float constant;
			public float TriggerThreshold;
			public float StickThreshold;
			public float TimeAccelerationPercent;
			public KeyDirection Direction;

			public EventReader GetReader()
			{
				return new EventReader(this);
			}

			public override bool Equals(object obj)
			{
				return base.Equals(obj);
			}

			public bool IsEqual(Event ev)
			{
				if (
					Type.Equals(ev.Type) &&
					Code.Equals(ev.Code) &&
					Data.Equals(ev.Data) &&
					IsBoolean.Equals(ev.IsBoolean) &&
					SelectedAxis.Equals(ev.SelectedAxis) &&
					TimeAcceleration.Equals(ev.TimeAcceleration) &&
					Acceleration.Equals(ev.Acceleration) &&
					constant.Equals(ev.constant) &&
					TriggerThreshold.Equals(ev.TriggerThreshold) &&
					StickThreshold.Equals(ev.StickThreshold) &&
					EventTime.Equals(ev.EventTime) &&
					Direction.Equals(ev.Direction) &&
					TimeAccelerationPercent.Equals(ev.TimeAccelerationPercent)
					)
				{
					return true;
				}
				return false;
			}

			public override int GetHashCode()
			{
				return base.GetHashCode();
			}
		}

		public class EventReader
		{
			//----user32.dll IMPORT!!!-----//
			[DllImport("user32.dll")]
			public static extern uint SendInput(uint nInputs, InputData.INPUT[] pInputs, int cbSize);

			[DllImport("user32.dll")]
			public static extern bool SetCursorPos(int x, int y);

			private static List<string> FloatsOverThreshold = new List<string>();

			private Keymap.Event linkedEvent;
			public Keymap.Event LinkedEvent
			{
				get { return linkedEvent; }
			}

			public EventReader(Event link)
			{
				linkedEvent = link;

			}

			private float hysteresis { get { return Properties.Settings.Default.Hysteresis; } }

			/// <summary>
			/// Extremely complicated boolean evaluations that determine if the current event should execute based on the OnPress and threshold readings.
			/// Used by Execute(...)
			/// </summary>
			/// <param name="e"></param>
			/// <returns></returns>
			private bool shouldIExecute(ControllerInputEventArgs e)
			{
				
				if (e.type == ControllerInputType.ButtonPressed)
				{
					return e.ButtonPressed == (linkedEvent.EventTime == EventTrigger.OnPress);
				}
				if (e.type == ControllerInputType.TriggerPulled)
				{
					if (e.TriggerAxis >= (linkedEvent.TriggerThreshold + hysteresis))
					{
						if (FloatsOverThreshold.Contains(e.Name))
						{
							return false;
						}
						else
						{
							FloatsOverThreshold.Add(e.Name);
							return linkedEvent.EventTime == EventTrigger.OnPress;
						}
					}
					else if (e.TriggerAxis <= linkedEvent.TriggerThreshold - hysteresis)
					{
						if (FloatsOverThreshold.Contains(e.Name))
						{
							FloatsOverThreshold.Remove(e.Name);
							return true;
						}
						else
						{
							return false;
						}
					}
				}
				if (e.type == ControllerInputType.StickDisplaced)
				{
					if ((linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_MOVE) ||
						linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_WHEEL) ||
						linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_HWHEEL)) &&
						linkedEvent.Type == InputData.InputTypeToString(InputData.INPUTTYPE.INPUT_MOUSE)
						)
						return true;
					switch (linkedEvent.SelectedAxis)
					{
						case Axis.Xplus:
							if (e.XAxis >= (linkedEvent.StickThreshold + hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "+X"))
								{
									return false;
								}
								else
								{
									FloatsOverThreshold.Add(e.Name + "+X");
									return linkedEvent.EventTime == EventTrigger.OnPress;

								}
							}
							if (e.XAxis <= (linkedEvent.StickThreshold - hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "+X"))
								{
									FloatsOverThreshold.Remove(e.Name + "+X");
									return linkedEvent.EventTime == EventTrigger.OnRelease;
								}
							}
							return false;
						case Axis.Xminus:
							if (-e.XAxis >= (linkedEvent.StickThreshold + hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "-X"))
								{
									return false;
								}
								else
								{
									FloatsOverThreshold.Add(e.Name + "-X");
									return linkedEvent.EventTime == EventTrigger.OnPress;

								}
							}
							if (-e.XAxis <= (linkedEvent.StickThreshold - hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "-X"))
								{
									FloatsOverThreshold.Remove(e.Name + "-X");
									return linkedEvent.EventTime == EventTrigger.OnRelease;
								}
							}
							return false;
						case Axis.Yplus:
							if (e.YAxis >= (linkedEvent.StickThreshold + hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "+Y"))
								{
									return false;
								}
								else
								{
									FloatsOverThreshold.Add(e.Name + "+Y");
									return linkedEvent.EventTime == EventTrigger.OnPress;

								}
							}
							if (e.YAxis <= (linkedEvent.StickThreshold - hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "+Y"))
								{
									FloatsOverThreshold.Remove(e.Name + "+Y");
									return linkedEvent.EventTime == EventTrigger.OnRelease;
								}
							}
							return false;
						case Axis.Yminus:
							if (-e.YAxis >= (linkedEvent.StickThreshold + hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "-Y"))
								{
									return false;
								}
								else
								{
									FloatsOverThreshold.Add(e.Name + "-Y");
									return linkedEvent.EventTime == EventTrigger.OnPress;

								}
							}
							if (-e.YAxis <= (linkedEvent.StickThreshold - hysteresis))
							{
								if (FloatsOverThreshold.Contains(e.Name + "-Y"))
								{
									FloatsOverThreshold.Remove(e.Name + "-Y");
									return linkedEvent.EventTime == EventTrigger.OnRelease;
								}
							}
							return false;
						default: return false;
					}
				}

				return false;
			}

			/// <summary>
			/// Provides key direction for a key press based on controller input and linked.threshold settings
			/// </summary>
			/// <param name="e"></param>
			/// <returns></returns>
			private KeyDirection keyDirection(ControllerInputEventArgs e)
			{
				if (e.type == ControllerInputType.ButtonPressed)
				{
					if (e.ButtonPressed == true)
						return KeyDirection.Down;
					return KeyDirection.Up;
				}
				else if (e.type == ControllerInputType.TriggerPulled)
				{
					if (e.TriggerAxis >= linkedEvent.TriggerThreshold + hysteresis)
						return KeyDirection.Down;
					return KeyDirection.Up;
				}
				else if (e.type == ControllerInputType.StickDisplaced)
				{
					///TODO REMEMBER AXIS X+/-Y+/-
					switch (linkedEvent.SelectedAxis)
					{
						case Axis.Xplus:
							if (e.XAxis >= linkedEvent.StickThreshold + hysteresis)
							{
								return KeyDirection.Down;
							}
							else
							{
								return KeyDirection.Up;
							}
						case Axis.Xminus:
							if (-e.XAxis >= linkedEvent.StickThreshold + hysteresis)
							{
								return KeyDirection.Down;
							}
							else
							{
								return KeyDirection.Up;
							}
						case Axis.Yplus:
							if (e.YAxis >= linkedEvent.StickThreshold + hysteresis)
							{
								return KeyDirection.Down;
							}
							else
							{
								return KeyDirection.Up;
							}
						case Axis.Yminus:
							if (e.YAxis >= linkedEvent.StickThreshold + hysteresis)
							{
								return KeyDirection.Down;
							}
							else
							{
								return KeyDirection.Up;
							}
						default: return KeyDirection.Up;


					}

				}
				return KeyDirection.Down;
			}

			/// <summary>
			/// Sends command imported from user32.dll to execute the required action.
			/// </summary>
			/// <param name="e"></param>
			public void Execute(ControllerInputEventArgs e)
			{

				if (shouldIExecute(e))
				{


					InputData.INPUT input = new InputData.INPUT();
					if (InputData.StringToInputType(linkedEvent.Type) == InputData.INPUTTYPE.INPUT_KEYBOARD)
					{
						input.type = InputData.INPUT_KEYBOARD;
						input.ki.wVk = (ushort)InputData.StringToVK(linkedEvent.Code);
						if (linkedEvent.Direction == KeyDirection.Up)
						{
							input.ki.dwFlags = 2;
						}
						else
						{
							input.ki.dwFlags = 0;
						}
						input.ki.dwExtraInfo = 0;
						InputData.INPUT[] k;
						k = new InputData.INPUT[1];
						k[0] = input;
						SendInput(1, k, InputData.KebdInputSize);
					}

					if (InputData.StringToInputType(linkedEvent.Type) == InputData.INPUTTYPE.INPUT_MOUSE)
					{
						input.type = InputData.INPUT_MOUSE;
						if (!Cursor.Position.Equals(new System.Drawing.Point((int)XMouseApplication.CursorPositionX, (int)XMouseApplication.CursorPositionY)))
						{
							XMouseApplication.CursorPositionX = (float)Cursor.Position.X;
							XMouseApplication.CursorPositionY = (float)Cursor.Position.Y;
						}
						if (XMouseApplication.DebugMessages)
							MessageBox.Show(String.Format("Recorded Cursor Position: {0},{1}", XMouseApplication.CursorPositionX, XMouseApplication.CursorPositionY));
						if (linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_MOVE))
						{
							input.mi.dwFlags = (uint)(InputData.MOUSEEVENT.MOUSEEVENTF_MOVE | InputData.MOUSEEVENT.MOUSEEVENTF_ABSOLUTE);
							double movementMagnitude = Math.Sqrt(Math.Pow(e.XAxis, 2.0) + Math.Pow(e.YAxis, 2.0));
							double Yaxis = -e.YAxis / movementMagnitude;
							double Xaxis = e.XAxis / movementMagnitude;
							movementMagnitude *= linkedEvent.constant;
							movementMagnitude = Math.Pow(movementMagnitude, linkedEvent.Acceleration);
							//if (XMouseApplication.LastMovement.Ticks + XMouseApplication.RefreshPeriod * 30000 < DateTime.Now.Ticks)
							//{
							//	XMouseApplication.OldMovement = 0.0;
							//}
							if (linkedEvent.TimeAcceleration)
							{
								if (XMouseApplication.OldMovement < movementMagnitude)
								{
									movementMagnitude = XMouseApplication.OldMovement * (linkedEvent.TimeAccelerationPercent / 100) + movementMagnitude * (1 - (linkedEvent.TimeAccelerationPercent / 100));
								}

							}
							
							XMouseApplication.OldMovement = movementMagnitude;
							XMouseApplication.LastMovement = DateTime.Now;
							if (movementMagnitude != 0)
							{
								input.mi.dx = (Int32)(XMouseApplication.CursorPositionX + (Xaxis * movementMagnitude));
								input.mi.dy = (Int32)(XMouseApplication.CursorPositionY + (Yaxis * movementMagnitude));
								XMouseApplication.CursorPositionX += (Xaxis * movementMagnitude);
								XMouseApplication.CursorPositionY += (Yaxis * movementMagnitude);
							}
							else
							{
								input.mi.dx = Cursor.Position.X;
								input.mi.dy = Cursor.Position.Y;
							}
							input.mi.time = 0;
							input.mi.dwExtraInfo = 0;
							input.mi.mouseData = 0;
							InputData.INPUT[] inputs = new InputData.INPUT[1] { input };
							SetCursorPos((int)XMouseApplication.CursorPositionX, (int)XMouseApplication.CursorPositionY);
						}
						else if (linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_WHEEL))
						{
							input = new InputData.INPUT();
							input.type = InputData.INPUT_MOUSE;
							input.mi.dwFlags = (uint)InputData.MOUSEEVENT.MOUSEEVENTF_WHEEL;
							if (linkedEvent.IsBoolean)
							{
								input.mi.mouseData = (int)linkedEvent.constant;
							}
							else
							{
								double ScrollMagnitude = Math.Sqrt(Math.Pow(e.XAxis, 2) + Math.Pow(e.YAxis, 2));
								double xaxis = e.XAxis / ScrollMagnitude;
								double yaxis = e.YAxis / ScrollMagnitude;

							}
						}
						else if (linkedEvent.Code == InputData.MouseEventToString(InputData.MOUSEEVENT.MOUSEEVENTF_HWHEEL))
						{

						}
						else //if(MOUSEEVENT.MOUSEEVENTF_LEFTDOWN//RIGHTDOWN/LEFTUP/RIGHTUP/MIDDLEDOWN/UP
						{
							input = new InputData.INPUT();
							input.type = InputData.INPUT_MOUSE;
							input.mi.dwFlags = (uint)InputData.StringToMouseEvent(linkedEvent.Code);
							input.mi.dx = Cursor.Position.X;
							input.mi.dy = Cursor.Position.Y;
							input.mi.dwExtraInfo = 0;
							input.mi.time = 0;
							input.mi.mouseData = 0;
							InputData.INPUT[] inputs = new InputData.INPUT[1] { input };
							SendInput(1, new InputData.INPUT[1] { input }, Marshal.SizeOf(input));
							//MessageBox.Show(linkedEvent.Code);

						}

					}

					if(linkedEvent.Type == InputData.InputTypeToString(InputData.INPUTTYPE.INPUT_OTHER))
					{
						//UI_CODE
						if(linkedEvent.Code == "Switcher")
						{

						}
						if(linkedEvent.Code == "Keyboard")
						{

						}
						if(linkedEvent.Code == "Settings")
						{

						}
						if(linkedEvent.Code == "Keylock")
						{

						}


					}


				} //end if(shouldIExecute())
			}
		}
	}



	public enum Axis
	{
		Xplus,
		Yplus,
		Xminus,
		Yminus
	}



	public enum EventTrigger
	{
		OnRelease,
		OnPress
	}



	public enum KeyDirection
	{
		Down,
		Up,
		DownUp
	}

}
